﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AdAggregator.Advertisement;
using AdAggregator.Attribute;
using AdAggregator.Parser;
using log4net;

namespace AdAggregator.Util
{
    public class DuplicateRemover<TAdvertisement> where TAdvertisement : IAdvertisement
    {
        private static readonly ILog Log = LogManager.GetLogger(typeof(DuplicateRemover<TAdvertisement>));

        public static IParseResult<TAdvertisement> GetResultWithoutDuplicates(IParseResult<TAdvertisement> originalResult)
        {
            var finalResult = new ParseResult<TAdvertisement>
            {
                AllAvailable = originalResult.AllAvailable,
                LastSearchPage = originalResult.LastSearchPage
            };
            finalResult.Results.AddRange(originalResult.Results);
            var duplicates = new List<TAdvertisement>();
            foreach (var left in finalResult.Results)
            {
                foreach (var right in finalResult.Results)
                {
                    if (left.Equals(right)) continue;
                    if (IsDuplicate(left, right) && !duplicates.Contains(right) && !duplicates.Contains(left))
                        duplicates.Add(right);
                }
            }
            var duplicateCount = duplicates.Count;
            finalResult.AllAvailable -= duplicateCount;
            finalResult.Results.RemoveAll(duplicates.Contains);
            return finalResult;
        }

        private static bool IsDuplicate(TAdvertisement left, TAdvertisement right)
        {
            if (left.Link == right.Link)
            {
                Log.Debug(String.Format("Following ads marked as duplicates:\n[{0}]\n[{1}]", right.Link, left.Link));
                return true;
            }
            var properties = typeof(TAdvertisement).GetProperties()
                .Where(p => p.IsDefined(typeof(Comparable)) && p.GetValue(left) != null && p.GetValue(right) != null);
            return IsDuplicateByProperties(left, right, properties);
        }

        private static bool IsDuplicateByProperties(TAdvertisement left, TAdvertisement right, IEnumerable<PropertyInfo> properties)
        {
            var propertyInfos = properties as IList<PropertyInfo> ?? properties.ToList();
            var stringProperties = propertyInfos.Where(p => p.PropertyType == typeof(string));
            var numericProperties = propertyInfos.Where(p => p.PropertyType == typeof(decimal?) || p.PropertyType == typeof(int?));
            var potentiallyDuplicateProperties = new List<PropertyInfo>();
            foreach (var prop in stringProperties)
            {
                if (prop.GetValue(left).ToString().ToLowerInvariant().ToLatinString() !=
                    prop.GetValue(right).ToString().ToLowerInvariant().ToLatinString()) return false;
                potentiallyDuplicateProperties.Add(prop);
            }
            foreach (var prop in numericProperties)
            {
                var error = prop.GetCustomAttribute<Comparable>().Error;
                var leftValObj = prop.GetValue(left);
                var rightValObj = prop.GetValue(right);
                if (leftValObj is decimal? && rightValObj is decimal?)
                {
                    var leftVal = (decimal?)leftValObj;
                    var rightVal = (decimal?)rightValObj;
                    if (Math.Abs(leftVal.Value - rightVal.Value) > error) return false;
                    potentiallyDuplicateProperties.Add(prop);
                }
                else if (leftValObj is int? && rightValObj is int?)
                {
                    var leftVal = (int?)leftValObj;
                    var rightVal = (int?)rightValObj;
                    if (Math.Abs(leftVal.Value - rightVal.Value) > error) return false;
                    potentiallyDuplicateProperties.Add(prop);
                }
                else
                {
                    return false;
                }
            }
            var isDuplicateByImportance = IsDuplicateByImportance(potentiallyDuplicateProperties);
            if (isDuplicateByImportance)
            {
                Log.Debug(String.Format("Following ads marked as duplicates:\n[{0}]\n[{1}]", left.Link, right.Link));
            }
            return isDuplicateByImportance;
        }

        private static bool IsDuplicateByImportance(IEnumerable<PropertyInfo> properties)
        {
            var duplicateThreshold = ComputeDuplicateThreshold();
            var importanceSum = properties.Sum(prop => prop.GetCustomAttribute<Comparable>().Importance);
            return importanceSum > duplicateThreshold;
        }

        private static int ComputeDuplicateThreshold()
        {
            const double thresholdFactor = 0.7;
            var myAdvertisementComparableProperties =
                typeof(TAdvertisement).GetProperties().Where(p => p.IsDefined(typeof(Comparable)));
            var sumOfImportances =
                myAdvertisementComparableProperties.Sum(p => p.GetCustomAttribute<Comparable>().Importance);
            return (int)(sumOfImportances * thresholdFactor);
        }
    }
}